package bgpemulation;

import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;

public class BGPMessage {

	//	path attribute information
	int originFlag = 1;
	int ASPathFlag = 2;
	int nextHopFlag = 3;
	
	int IPAddrComponents = 5; // number of parts to an IP address - the first four parts, and the prefix
	
	int ipAddrComponents = 5;
	
	public BGPMessage()
	{
		
	}
	
	// For testing purposes
	public static void main(String[] args)
	{
		BGPMessage bgphelper = new BGPMessage();
		
		List<CIDRPrefix> withdrawnIPs = new LinkedList<CIDRPrefix>();
		List<CIDRPrefix> reachableIPs = new LinkedList<CIDRPrefix>();
		List<Byte> asPath = new LinkedList<Byte>();
		
		
		withdrawnIPs.add(new CIDRPrefix("12.10.13.4/24"));
		withdrawnIPs.add(new CIDRPrefix("12.10.13.5/24"));
		withdrawnIPs.add(new CIDRPrefix("12.10.13.6/24"));
		
		asPath.add((byte)2);
		
		System.out.println(withdrawnIPs.size());
		System.out.println(reachableIPs.size());
		
		byte[] newPayload = bgphelper.createBGPMessage(2, withdrawnIPs, reachableIPs, asPath);
		
		//bgphelper.processBGPMessage(newPayload);
		
		//bgphelper.printByteArray(newPayload);
	}
	
	public byte[] createBGPMessage(int type, List<CIDRPrefix> withdrawnIPs, List<CIDRPrefix> reachableIPs, List<Byte> asPath)
	{
		// determine size for buffer
		// space for: 														to get values:
		//				type of BGP message (byte),		1					get(0)
		//				total length of message (int), 	4					getInt(1)
		//				length of withdrawn IPs (int),	4					getInt(5)
		//				withdrawn IPs (number of IPs * ipAddrComponents),	
		//				length of reachable IPs (int),	4
		//				reachable IPs (number of IPs * ipAddrComponents),
		//				length of AS-path (int)			4
		//				AS-path 						
		//	4 + 1 + 4 + (number of withdrawn IPs * 5) + 4 + (number of reachable IPs * 5) + 4 + asPath.size()
		
		int buffSize = computeByteBufferLength(withdrawnIPs, reachableIPs, asPath);
		//System.out.println("Buffer size: " + buffSize);
		
		ByteBuffer byteBuffer = ByteBuffer.allocate(buffSize);
		
		// add stuff to byteBuffer
		putIntoByteBuffer(type, byteBuffer, withdrawnIPs, reachableIPs, asPath);
				
		return byteBuffer.array();
	}


	private void putIntoByteBuffer(int type, ByteBuffer byteBuffer,
			List<CIDRPrefix> withdrawnIPs, List<CIDRPrefix> reachableIPs,
			List<Byte> asPath) {
		// TODO Auto-generated method stub
		//byteBuffer.putInt(byteBuffer.capacity()).put((byte)type).putInt(withdrawnIPs.size());
		
		byteBuffer.put((byte)type).putInt(byteBuffer.capacity()).putInt(withdrawnIPs.size());
		
		// put withdrawn IPs into byteBuffer
		writeIPsToByteBuffer(byteBuffer, withdrawnIPs);
		
		// put number of reachable IPs into byteBuffer
		byteBuffer.putInt(reachableIPs.size());
		
		// put reachable IPs into byteBuffer
		writeIPsToByteBuffer(byteBuffer, reachableIPs);
		
		// put length of AS path into byteBuffer
		byteBuffer.putInt(asPath.size());
		
		// put AS path into byteBuffer
		writeASPathtoByteBuffer(byteBuffer, asPath);
		
	}
	
	private void writeASPathtoByteBuffer(ByteBuffer bb, List<Byte> asPath)
	{
		for (byte b : asPath)
		{
			bb.put(b);
		}
	}
	public void processBGPMessage(byte[] ba)
	{
		ByteBuffer bb = ByteBuffer.wrap(ba);
		
		int msgLength = bb.getInt(1);
		byte msgType = bb.get(0);
		
		int count = 5;
		
		System.out.println("Message length: " + msgLength);
		
		if ((int)msgType == 2)
		{
			System.out.println("Message type: " + msgType + " - Is an update message");
		}
		else
			System.out.println("Is another type of BGP message");
		
		int numWithdrawnIPs = bb.getInt(count);
		System.out.println("Number of withdrawn IPs: " + numWithdrawnIPs);
		
		count += 4;
		
		for (int i = 0; i < numWithdrawnIPs; i++)
		{
			byte[] ip = new byte[ipAddrComponents];
			
			for (int j = 0; j < ipAddrComponents; j++)
			{
				ip[j] = bb.get(count);
				count++;
			}
			System.out.println(new CIDRPrefix(ip).getStringPrefix());
		}
		
		// Prints current count - for debugging
		//System.out.println(count);
		
		int numReachableIPs = bb.getInt(count);
		
		
		if (numReachableIPs > 0)
		{
			System.out.println("Has reachable IPs");
			
			// iterate through reachable IPs
			for (int i = 0; i < numReachableIPs; i++)
			{
				byte[] ip = new byte[ipAddrComponents];
				
				for (int j = 0; j < ipAddrComponents; j++)
				{
					ip[j] = bb.get(count);
					count++;
				}
				System.out.println(new CIDRPrefix(ip).getStringPrefix());
			}
		}
		else
		{
			System.out.println("No reachable IPs");
		}
		
		// Prints current count - for debugging
		//System.out.println(count);
		
		count+=4;
		
		int lengthASPath = bb.getInt(count);
		System.out.println("Length of AS-path: " + lengthASPath + ", index: " + count);
		
		System.out.println("int value at " + (count-1) + ": " + bb.getInt(count-1));
		System.out.println("int value at " +count + ": " + bb.getInt(count));
		System.out.println("int value at " + (count+1) + ": " + bb.getInt(count+1));
		
		count += 4;
		//System.out.println("Current index: " + count);
		
		
		List<Byte> asPath = new LinkedList<Byte>(); 
		for (int i = 0; i < lengthASPath; i++)
		{
			asPath.add(bb.get(count));
			count++;
		}
		
		for (byte b : asPath)
		{
			System.out.print(b + " ");
		}
		System.out.println();
	}
	
	private int computeByteBufferLength(List<CIDRPrefix> withdrawnIPs, 
										List<CIDRPrefix> NLRI, 
										List<Byte> ASPath)
	{
		int bufferSize = 4;
		bufferSize += 1;
		bufferSize += 4;
		
		int unfeasibleRouteLength = withdrawnIPs.size() * IPAddrComponents;
		bufferSize += unfeasibleRouteLength;
		
		//	get lengths of path attributes individual parts
		int pathAttributesLength = 4;
		pathAttributesLength += ASPath.size();
		
		bufferSize += pathAttributesLength;
		
		bufferSize += 4;
		
		//	get lengths of NLRI individual parts
		int NLRILength = NLRI.size() * IPAddrComponents;
		bufferSize += NLRILength;
		
		//BGPMessageLengthTotal = bufferSize;
		
		System.out.println(bufferSize);
		
		return bufferSize;
	}
	
	private void writeIPsToByteBuffer(ByteBuffer bb, List<CIDRPrefix> IPs)
	{	
		for (CIDRPrefix ip : IPs)
		{
			byte[] ipBytes = ip.getBytePrefix();
			
			for (byte ipPart : ipBytes)
			{
				bb.put(ipPart);
				//bb.position(bb.position() + 1);
			}
		}
	}
	
	public static void printByteArray(byte[] ba)
	{
		System.out.println("");
		System.out.println("PRINTING BYTE ARRAY");
		for (int i = 0; i < ba.length; i++)
		{
			System.out.println("[" + i + "]: " + ba[i]);
		}
		System.out.println("");
	}
	
	public int getMessageLength(ByteBuffer bb)
	{
		return bb.getInt(1);
	}
	
	public List<CIDRPrefix> getWithdrawnIPs(ByteBuffer bb)
	{
//		System.out.println("Attempting to retrieve withdrawn IPs");
//		for (int i = 0; i < bb.capacity(); i++)
//		{
//			System.out.println(i + " -> " + (bb.get(i) & 0xff));
//		}
		int index = 5;		// index to start iterating from - starts at index to get number of withdrawn IPs
		List<CIDRPrefix> withdrawnIPs = new LinkedList<CIDRPrefix>();
		
//		System.out.println("Index: " + index);
		int numIPs = bb.getInt(index);
//		System.out.println("Value at " + index + ": " + numIPs);
		index+=4;
		if (numIPs == 0)
		{
			System.out.println("No withdrawn IPs");
			return withdrawnIPs;
		}
		else
		{
			System.out.println(numIPs + " withdrawn IPs");
			for (int i = 0; i < numIPs; i++)
			{
				byte[] ip = new byte[IPAddrComponents];
				for (int j = 0; j < IPAddrComponents; j++)
				{
					ip[j] = (byte) (bb.get(index) & 0xff);
					index++;
				}
				withdrawnIPs.add(new CIDRPrefix(ip));
			}
			return withdrawnIPs;
		}
	}
	
	public List<CIDRPrefix> getReachableIPs(ByteBuffer bb)
	{
//		for (int i = 0; i < bb.capacity(); i++)
//		{
//			System.out.println(i + " -> " + (bb.get(i) & 0xff));
//		}
		
		int index = 5;		// index to start iterating from - starts at index to get number of withdrawn IPs
		List<CIDRPrefix> reachableIPs = new LinkedList<CIDRPrefix>();
		
		int numWithdrawnIPs = bb.getInt(index);
		
		int numReachableIPs = 0;
		
		if (numWithdrawnIPs == 0)
		{
			index += 4;
//			System.out.println(index);
			numReachableIPs = bb.getInt(index);
//			System.out.println("Value at " + index + ": " + bb.getInt(index));
		}
		else	// NOT TESTED YET
		{
			index += 4;
			index += (numWithdrawnIPs * IPAddrComponents);
			
//			System.out.println(index);
			numReachableIPs = bb.getInt(index);
			
//			System.out.println("Value at " + index + ": " + bb.getInt(index));
		}
		
		System.out.println(numReachableIPs + " reachable IPs");
//		System.out.println("Current index is: " + index);
		index += 4;
		for (int i = 0; i < numReachableIPs; i++)
		{
			byte[] ip = new byte[IPAddrComponents];
			for (int j = 0; j < IPAddrComponents; j++)
			{
				ip[j] = (byte) (bb.get(index) & 0xff);
				index++;
			}
			reachableIPs.add(new CIDRPrefix(ip));
		}
		
		return reachableIPs;
	}

	public List<Byte> getASPath(ByteBuffer bb, List<CIDRPrefix> withdrawnIPs, List<CIDRPrefix> reachableIPs) {
		// TODO Auto-generated method stub
//		for (int i = 0; i < bb.capacity(); i++)
//		{
//			System.out.println(i + " -> " + (bb.get(i) & 0xff));
//		}		
		
		List<Byte> asPath = new LinkedList<Byte>();
		
		int index = ((withdrawnIPs.size() + reachableIPs.size()) * IPAddrComponents) + 13;
//		System.out.println("Current index: " + index);
		
		int lengthASPath = bb.getInt(index);
		
		index += 4;	// skips ahead to first AS-path entry
		
//		System.out.println("Next index: " + index);	//22
		
		for (int i = 0; i < lengthASPath; i++)
		{
			byte b = bb.get(index + i);
			asPath.add((byte)(b & 0xff));
		}
		return asPath;
	}
	
}
